Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support remote and local assets in custom CSS #1372

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open

Conversation

Fil
Copy link
Contributor

@Fil Fil commented May 20, 2024

This includes images and fonts using url().

Using onResolve (as suggested in #786 (comment))

closes #786
supersedes #788

Capture d’écran 2024-05-20 à 19 58 41

The test above uses a remote @font-face and a local background asset (horse.jpg); but any file type is supported (so you can finally have the custom cursors you've been dreaming of).

TODO:

  • use a resolver hook
  • support css @import

(This includes images and fonts using url().)

Using onResolve (as suggested in #786 (comment))

closes #786
supersedes #788
src/rollup.ts Outdated Show resolved Hide resolved
src/build.ts Outdated Show resolved Hide resolved
Copy link
Member

@mbostock mbostock left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, this is a useful addition!

Some nits, plus I think there is some plumbing missing: getResolvers should now resolve the local assets that are referenced by local stylesheets. Then I don’t think you’ll need to accumulate the files separately during build, since getResolvers will have done it already. And that would mean that we’d also now be watching these referenced files during preview, so we’d get hot replacement if you edit a local file referenced by a local stylesheet. (Though we’ll also need to add ?sha… for references to local files from local stylesheets as mentioned in the comments.)

src/build.ts Outdated Show resolved Hide resolved
src/build.ts Outdated Show resolved Hide resolved
src/build.ts Outdated
aliases.set(resolveStylesheetPath(root, specifier), alias);
await effects.writeFile(alias, contents);
delayedStylesheets.add(specifier);
for (const file of (await bundleStyles({path: join(root, specifier)})).files) files.add(file);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be cleaner to have two separate methods (that use the same underlying implementation) since we always call bundleStyles in one of two ways: bundleStyles could return the contents as before, and resolveStyleFiles could return the set of files. But low priority…

src/build.ts Outdated Show resolved Hide resolved
src/rollup.ts Outdated Show resolved Hide resolved
src/rollup.ts Outdated
build.onResolve({filter: /./}, (args) => {
if (args.path.endsWith(".css") || args.path.match(/^[#.]/)) return;
files.add(args.path);
const path = join("..", aliases?.get(args.path) ?? join("_file", args.path));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another big advantage of the resolve hook is that bundleStyles won’t have to know about the _file directory — that logic can be supplied by the build command.

And for the preview command, ideally we’d supply a resolve hook that does ?sha=… so that module replacement works when you update a file referenced by a local stylesheet?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now we pass the marked URL, but I wasn't able to positively test hot module replacement.

fix a bug where we would rewrite data: urls
Copy link
Member

@mbostock mbostock left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me know if you want help moving the logic into getResolvers (or maybe this gets put on hiatus for a bit while we focus on other things).

@Fil
Copy link
Contributor Author

Fil commented May 23, 2024

I think it's close to done? (I'm not sure what you have in mind so that it can be finished.)

@mbostock
Copy link
Member

Have you pushed your changes? I don’t see any changes to getResolvers here.

@Fil
Copy link
Contributor Author

Fil commented May 23, 2024

I changed bundleStyles to accept a resolve hook, to which I pass a function that uses getResolvers — but I don't understand what needs to be changed in getResolvers.

@mbostock
Copy link
Member

We need to parse the stylesheets and resolve/collect all the transitive dependencies. We can pair on it sometime.

@Fil
Copy link
Contributor Author

Fil commented May 23, 2024

I think it works already, see for example https://github.com/observablehq/framework/pull/1372/files#diff-8adbd6b9683bb1d5268aa477247f4c6edf9ea93e5b13397535ff450b4d57ae38
style.css loads atkinson.css which loads the font. (To demonstrate further I should maybe add a local resource to atkinson.css — but I've tried and it works too.) Unless I'm missing something, the one thing that doesn't work is watching and HMR.

@mbostock
Copy link
Member

I’m aware it works, but I think we should implement it by moving it into getResolvers because that’s where it belongs. This will fix the watching/hot replacement issue.

@choucavalier
Copy link

Been trying to import self-hosted font assets and stumbled upon this PR. It would be great to be able to define @font-face indeed!

@mbostock
Copy link
Member

mbostock commented Jul 7, 2024

I think there’s probably a lot more that we want to do here, and I haven’t figured out yet if it makes sense to try to do it all together or if there’s a more piecemeal approach. Specifically, what about inline styles like this?

<style type="text/css">

body {
  background-image: url("image.png");
}

</style>

We could detect the reference to image.png above and rewrite the CSS to reference the file in _file. (Note that we couldn’t support body.style.backgroundImage = 'url("image.png")' in JavaScript because it’s not statically analyzable, but you could use a FileAttachment.)

Similarly, given this:

<link rel="stylesheet" href="test.css" type="text/css">

And the following stylesheet:

body {
  background-image: url("image.png");
}

Presumably we’d want to rewrite test.css to reference image.png in _file too.

And then there’s the question of whether test.css should be bundled into a single stylesheet, or if we should only rewrite the stylesheets to correct for references to local assets?

I want to think about this some more.

@shadoof
Copy link

shadoof commented Jul 23, 2024

Been trying to import self-hosted font assets and stumbled upon this PR. It would be great to be able to define @font-face indeed!

Allowing @font-face to import font files that are local to a project would be good practice for possible archiving.

@mbostock
Copy link
Member

I realized we didn’t have a tracking issue for this, only the font-face crash bug #786, so I filed #1573.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

esbuild crashes when style.css contains @font-face definitions
4 participants